package com.symaster.common.test;

import com.symaster.common.geom.PolygonOffsetEditing;
import com.symaster.common.geom.ViewPolygonPanel;
import com.symaster.common.geom.entity.Node;
import com.symaster.common.geom.entity.Point;
import com.symaster.common.geom.entity.Polygon;
import com.symaster.common.geom.util.PointUtil;
import com.symaster.common.image.util.ImageUtil;
import com.symaster.common.optimization.Heredity;
import com.symaster.common.swing.FpsManager;
import com.symaster.common.swing.util.SwingUtil;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
 * @author yinmiao
 * @version 2023/11/8
 */
public class HeredityTest2 {

    public static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 3 * 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.DiscardOldestPolicy());

    public static void main(String[] args) {

        // 7662 * 5277

        ImageUtil.setupCvCore();

        Mat mat = Imgcodecs.imread("C:\\Users\\18790\\Downloads\\KT15772A03-3T29-S.jpg");
        List<com.symaster.common.geom.entity.Polygon> polygon1 = ImageUtil.getPolygon(mat,
                new Scalar(0, 0, 0),
                new Scalar(245, 245, 245),
                Imgproc.RETR_EXTERNAL,
                Imgproc.CHAIN_APPROX_TC89_KCOS);

        GraphicalData graphicalData = new GraphicalData();
        OptimalSolution optimalSolution = new OptimalSolution();
        HeredityOperator heredityOperator = new HeredityOperator(graphicalData);

        for (int i = 0; i < polygon1.size(); i++) {
            Polygon polygon = PointUtil.approxPolyDp(polygon1.get(i), 1);
            Node node = new Node(polygon, 10);
            node.setId(i);
            node.move(0, 0);
            graphicalData.nodes.add(node);
        }

        // LayoutPanel layoutPanel = new LayoutPanel(7662, - 1);
        // long s = System.currentTimeMillis();
        // for (Node node : graphicalData.nodes) {
        //     layoutPanel.addNode(node);
        // }
        // long e = System.currentTimeMillis();
        // System.out.println(e - s);

        openOptionDialog(graphicalData);
        // autoLayout(graphicalData, heredityOperator, optimalSolution);
    }

    private static void autoLayout(GraphicalData graphicalData, HeredityOperator heredityOperator, OptimalSolution optimalSolution) {
        int[] ints = new int[graphicalData.nodes.size()];
        for (int i = 0; i < ints.length; i++) {
            ints[i] = i;
        }
        Heredity heredity = new Heredity(ints, 4);
        heredity.setOperator(heredityOperator);
        heredity.setUpdateFinListener((g, fin) -> {
            graphicalData.fin = fin;
            // optimalSolution.geneList.add(g)
        });
        heredity.start();

        try {
            TimeUnit.SECONDS.sleep(20);
            Heredity.FractionObj stop = heredity.stop();
            System.out.println("stop.getCount() = " + stop.getCount());
            System.out.println("stop.getFraction() = " + stop.getFraction());

            PolygonOffsetEditing<Node> nodePolygonOffsetEditing = PolygonOffsetEditing.openWindow();
            Object obj = stop.getObj();
            if (obj instanceof LayoutPanel) {
                LayoutPanel layoutPanel = (LayoutPanel) obj;
                for (Node value : layoutPanel.getNodeIdMap().values()) {
                    nodePolygonOffsetEditing.addElement(value);
                }
            }

        } catch (InterruptedException | TimeoutException ex) {
            throw new RuntimeException(ex);
        }
    }

    private static void openOptionDialog(GraphicalData graphicalData) {
        final int[] index = new int[]{1};

        JFrame jFrame = new JFrame("OptionDialog");
        JPanel jPanel = new JPanel();

        LayoutView layoutPanel = new LayoutView(7662, -1);
        JFrame[] frame = new JFrame[1];

        JButton open = new JButton("Open");
        open.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                open.setEnabled(false);
                frame[0] = new JFrame("LayoutView");
                frame[0].setContentPane(layoutPanel.getView());
                frame[0].setPreferredSize(new Dimension(1200, 900));
                frame[0].pack();
                frame[0].setVisible(true);
                layoutPanel.nodeIdMap.clear();
                index[0] = 0;
            }
        });
        jPanel.add(open);

        JButton close = new JButton("Close");
        close.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (frame[0] != null) {
                    frame[0].dispose();
                    open.setEnabled(true);
                }
            }
        });
        jPanel.add(close);

        JPanel container1 = new JPanel();

        JComboBox<ComboBoxItem> comboBox1 = new JComboBox<>();
        JComboBox<ComboBoxItem> comboBox2 = new JComboBox<>();

        container1.add(comboBox1);
        for (Node node : graphicalData.nodes) {
            comboBox1.addItem(new ComboBoxItem(node.getId(), node.getId() + ""));
        }

        JButton button = new JButton("Add");
        button.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                threadPoolExecutor.execute(() -> {
                    Object selectedItem = comboBox1.getSelectedItem();
                    if (selectedItem instanceof ComboBoxItem) {
                        ComboBoxItem item = (ComboBoxItem) selectedItem;
                        comboBox1.removeItem(selectedItem);
                        comboBox2.addItem(item);
                        layoutPanel.addNode(graphicalData.nodes.get(item.id));
                    }
                });
            }
        });
        container1.add(button);
        jPanel.add(container1);

        JPanel container2 = new JPanel();
        container2.add(comboBox2);
        JButton reset = new JButton("Remove");
        reset.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Object selectedItem = comboBox2.getSelectedItem();
                if (selectedItem instanceof ComboBoxItem) {
                    ComboBoxItem item = (ComboBoxItem) selectedItem;
                    comboBox2.removeItem(selectedItem);
                    comboBox1.addItem(item);
                    layoutPanel.nodeIdMap.remove(item.id);
                    layoutPanel.updateUseWH();
                }
            }
        });
        container2.add(reset);
        jPanel.add(container2);

        jFrame.setContentPane(jPanel);
        jFrame.setPreferredSize(new Dimension(600, 700));
        jFrame.pack();
        SwingUtil.toCenter(jFrame);
        jFrame.setVisible(true);
    }

    public static class HeredityOperator implements Heredity.Operator {

        private final GraphicalData graphicalData;

        public HeredityOperator(GraphicalData graphicalData) {
            this.graphicalData = graphicalData;
        }

        @Override
        public Heredity.Result handler(int[] g) throws Exception {
            LayoutPanel layoutPanel = new LayoutPanel(7662, graphicalData.getMaxHeight());

            for (int i : g) {
                Node clone = graphicalData.getNodeById(i).clone();
                layoutPanel.addNode(clone);

                double fin = graphicalData.fin != null ? graphicalData.fin : 0;
                double v = graphicalData.getMaxHeight() - layoutPanel.getUseHeight();
                if (v < fin) {
                    return new Heredity.Result(0, null);
                }
            }

            double v = graphicalData.getMaxHeight() - layoutPanel.getUseHeight();
            return new Heredity.Result(v, layoutPanel);
        }
    }

    public static class OptimalSolution {
        public List<int[]> geneList = new ArrayList<>();
    }

    public static class GraphicalData {
        public List<Node> nodes = new ArrayList<>();
        private Double maxHeight;
        private Double fin;

        public Node getNodeById(int id) {
            for (Node node : nodes) {
                if (node.getId() != null && node.getId().equals(id)) {
                    return node;
                }
            }
            return null;
        }

        public double getMaxHeight() {
            if (maxHeight == null) {
                double sum = nodes.stream().mapToDouble(Node::getHeight).sum();
                int i = nodes.size() * 24;
                maxHeight = sum + i;
            }
            return maxHeight;
        }
    }

    public static class LayoutPanel implements ViewPolygonPanel {

        // ['y轴允许最大差距', '默认移动测试距离', '移动测试距离(不用管)', '精度']
        public final double[] configs = new double[]{0.1, 200.0, 0.0, 0.1};

        public final double width;
        public final double height;
        public final Map<Integer, Node> nodeIdMap;
        public double useWidth;
        public double useHeight;
        public double maxMarge; // 表示所有Node里面的最大Marge

        /**
         * 构建一个Node布局器
         *
         * @param width  指定宽度 (> 0)
         * @param height 指定高度, 如果设定为小于等于0则表示无限高度
         */
        public LayoutPanel(double width, double height) {
            this.width = width;
            this.height = height;
            this.nodeIdMap = new HashMap<>();
            this.useWidth = 0;
            this.useHeight = 0;
            this.maxMarge = 0;
            if (width <= 0) {
                throw new IllegalArgumentException("width <= 0");
            }
        }

        public void setStaticNodes(Collection<Node> staticNodes) {
            if (staticNodes == null || staticNodes.size() - 1 < 0) {
                return;
            }
            for (Node staticNode : staticNodes) {
                putToMap(staticNode, "(staticNode.getId() == null)", "(nodeIdMap.get(staticNode.getId()) != null)");
            }
        }

        public void addStaticNodes(Node... staticNodes) {
            if (staticNodes == null || staticNodes.length - 1 < 0) {
                return;
            }
            for (Node staticNode : staticNodes) {
                putToMap(staticNode, "(staticNode.getId() == null)", "(nodeIdMap.get(staticNode.getId()) != null)");
                updateUseWH();
            }
        }

        public void addNode(Node node) {
            putToMap(node, "(node.getId() == null)", "(nodeIdMap.get(node.getId()) != null)");
            initPoint(node.getId());
            autoLayout(node.getId());
            updateUseWH();
        }

        public double getWidth() {
            return width;
        }

        public double getHeight() {
            return height;
        }

        public Map<Integer, Node> getNodeIdMap() {
            return nodeIdMap;
        }

        public Double getUseWidth() {
            return useWidth;
        }

        public Double getUseHeight() {
            return useHeight;
        }

        public void updateUseWH() {
            this.useWidth = nodeIdMap.values().stream().mapToDouble(e -> e.getX() + e.getWidth()).max().orElse(0.0D);
            this.useHeight = nodeIdMap.values().stream().mapToDouble(e -> e.getY() + e.getHeight()).max().orElse(0.0D);
        }

        public void autoLayout(Integer id) {
            Node node = getNode(id);
            if (nodeIdMap.size() <= 1) {
                node.move(0, 0);
                return;
            }

            Point minPoint = new Point(node.getX(), node.getY());

            int testCount = 50;
            double v = node.getX() / testCount;

            double tX = node.getX();
            double tY = node.getY();

            double optimalXPoint = 0;

            for (int i = 0; i < testCount + 1; i++) {
                double testX = Math.min(i * v, tX);
                node.move(testX, tY);

                if (testY(node, minPoint)) {
                    optimalXPoint = testX;
                }
            }

            node.move(tX, minPoint.getY() + 10);
            autoLayout2(node, optimalXPoint, minPoint);
            node.move(minPoint);
        }

        public boolean testY(Node node, Point minPoint) {
            boolean flag = false;
            configs[2] = configs[1];
            while (configs[2] >= 1) {
                int i;
                while ((i = translateNode(node, 0, -configs[2])) == 0 || i == 1) {
                    if (i == 1) {
                        node.move(node.getX(), 0);
                    }
                    int i1 = minPoint(new Point(node.getX(), node.getY()), minPoint, configs[0]);
                    if (i1 == 1) {
                        flag = true;
                        minPoint.setX(node.getX());
                        minPoint.setY(node.getY());
                    }
                    if (i == 1) {
                        break;
                    }
                }

                configs[2] = configs[2] / 2.0;
            }
            return flag;
        }

        public void autoLayout2(Node node, double optimalXPoint, Point minPoint) {
            double testRange = width / 40;

            double tX = node.getX();
            double tY = node.getY();

            double low = range(optimalXPoint - testRange, 0, tX);
            double up = range(optimalXPoint + testRange, 0, tX);

            int testCount = 50;
            double v = (up - low) / testCount;
            for (int i = 0; i < testCount + 1; i++) {
                double testX = low + v * i;
                node.move(testX, tY);
                testY(node, minPoint);
            }

            node.move(minPoint.getX(), minPoint.getY());
        }

        public double range(double a, double min, double max) {
            if (a < min) {
                return min;
            }
            if (a > max) {
                return max;
            }
            return a;
        }

        public int minPoint(Point p1, Point p2, double yGap) {
            if (Math.abs(p1.getY() - p2.getY()) < yGap) {
                if (p1.getX() < p2.getX()) {
                    return 1;
                } else {
                    return 2;
                }
            } else {
                if (p1.getY() < p2.getY()) {
                    return 1;
                } else {
                    return 2;
                }
            }
        }

        /**
         * 移动指定 Node
         *
         * @param node node对象
         * @param xn   移动x距离
         * @param yn   移动y距离
         * @return 返回是否移动成功
         */
        public int translateNode(Node node, double xn, double yn) {
            node.translate(xn, yn);

            if (!notMoveOver(node.getId())) {
                node.translate(-xn, -yn);
                return 2;
            }

            /* 判断是否移出画布 */
            if (graphicsOutOfBounds(node)) {
                node.translate(-xn, -yn);
                return 1;
            }

            return 0;
        }

        public boolean notMoveOver(Integer id) {
            Node node = getNode(id);

            /* 测试是否与其他图形重叠 */
            // 寻找该Node一定范围内所有Node
            List<Node> nodes = fontNodesByRectangleArea((node.getX() - maxMarge), (node.getY() - maxMarge), (node.getWidth() + maxMarge * 2), (node.getHeight() + maxMarge * 2));

            for (Node nodeItem : nodes) {
                if (nodeItem.getId().equals(id)) {
                    continue;
                }

                if (isCollide(nodeItem, node)) {
                    return false;
                }
            }

            return true;
        }

        public boolean isCollide(Node node1, Node node2) {

            Polygon node1SwellPolygon = node1.getSwellPolygon();
            Polygon node2Polygon = node2.getPolygon();
            for (int i = 0; i < node2Polygon.getNpoints(); i++) {
                if (node1SwellPolygon.containsV3(node2Polygon.getXpoints()[i], node2Polygon.getYpoints()[i])) {
                    return true;
                }
            }

            Polygon node2SwellPolygon = node2.getSwellPolygon();
            Polygon node1Polygon = node1.getPolygon();
            for (int i = 0; i < node1Polygon.getNpoints(); i++) {
                if (node2SwellPolygon.containsV3(node1Polygon.getXpoints()[i], node1Polygon.getYpoints()[i])) {
                    return true;
                }
            }

            return false;
        }

        public List<Node> fontNodesByRectangleArea(double x, double y, double width, double height) {
            Collection<Node> values = nodeIdMap.values();
            ArrayList<Node> nodes = new ArrayList<>(values.size());
            for (Node node : values) {
                if (node.getX() > x + width) {
                    continue;
                }
                if (node.getY() > y + height) {
                    continue;
                }
                if (node.getX() + node.getWidth() < x) {
                    continue;
                }
                if (node.getY() + node.getHeight() < y) {
                    continue;
                }
                nodes.add(node);
            }
            return nodes;
        }

        /**
         * 判断指定Node是否在画布外
         * 注意: 该方法不会测试高度
         *
         * @param node Node对象
         * @return 返回True则代表Node在画布外, 否则在画布内
         */
        public boolean graphicsOutOfBounds(Node node) {
            if (node.getX() < 0 || node.getY() < 0) {
                return true;
            }
            return node.getX() + node.getWidth() > width;
        }

        public void initPoint(Integer id) {
            if (nodeIdMap.isEmpty()) {
                getNode(id).move(0, 0);
                return;
            }
            Node node = getNode(id);
            node.move(width - node.getWidth() - configs[3], useHeight + maxMarge + configs[3] + 50);
        }

        public Node getNode(Integer id) {
            return nodeIdMap.get(id);
        }

        public void putToMap(Node node, String s, String s1) {
            if (node.getId() == null) {
                throw new IllegalArgumentException(s);
            }
            if (nodeIdMap.get(node.getId()) != null) {
                throw new IllegalArgumentException(s1);
            }
            nodeIdMap.put(node.getId(), node);

            // 更新最大Marge
            this.maxMarge = Math.max(node.getMarge(), this.maxMarge);
        }

        @Override
        public Collection<Polygon> getPolygons() {
            return nodeIdMap.values().stream().map(Node::getPolygon).distinct().collect(Collectors.toList());
        }

        @Override
        public int size() {
            return nodeIdMap.size();
        }
    }

    public static class LayoutView extends LayoutPanel {

        private final LayoutViewComponent layoutViewComponent;

        /**
         * 构建一个Node布局器
         *
         * @param width  指定宽度 (> 0)
         * @param height 指定高度, 如果设定为小于0则表示无限高度
         */
        public LayoutView(double width, double height) {
            super(width, height);
            this.layoutViewComponent = new LayoutViewComponent(this);
        }

        @Override
        public void putToMap(Node node, String s, String s1) {
            super.putToMap(node, s, s1);
            layoutViewComponent.putNode(node);
        }

        @Override
        public boolean testY(Node node, Point minPoint) {
            FpsManager fpsManager = new FpsManager();

            boolean flag = false;
            configs[2] = configs[1];
            while (configs[2] >= 1) {
                int i;
                while ((i = translateNode(node, 0, -configs[2])) == 0 || i == 1) {
                    if (i == 1) {
                        node.move(node.getX(), 0);
                    }
                    int i1 = minPoint(new Point(node.getX(), node.getY()), minPoint, configs[0]);
                    if (i1 == 1) {
                        flag = true;
                        minPoint.setX(node.getX());
                        minPoint.setY(node.getY());
                    }
                    if (i == 1) {
                        break;
                    }

                    fpsManager.paintWait(120);
                }

                configs[2] = configs[2] / 2.0;
            }
            return flag;
        }

        public JComponent getView() {
            return layoutViewComponent;
        }
    }

    public static class LayoutViewComponent extends JComponent {
        private final LayoutView layoutView;
        private final Map<Integer, Image> nodeImageMap;
        private static final double allScale = 0.15;

        public LayoutViewComponent(LayoutView layoutView) {
            this.layoutView = layoutView;
            this.nodeImageMap = new HashMap<>();
        }

        public void putNode(Node node) {
            nodeImageMap.put(node.getId(), createImage(node));
        }

        private Image createImage(Node node) {
            Polygon clone = node.getPolygon().clone();
            clone.scaleTransform(new Point(0, 0), allScale);
            clone.move(0, 0);

            BufferedImage bufferedImage = new BufferedImage((int) Math.ceil(clone.getWidth()), (int) Math.ceil(clone.getHeight()), BufferedImage.TYPE_4BYTE_ABGR);
            Graphics graphics = bufferedImage.getGraphics();
            SwingUtil.antialiasingOn((Graphics2D) graphics);

            graphics.setColor(new Color(0, 0, 0, 0));
            graphics.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());

            graphics.setColor(new Color(25, 25, 25, 255));
            graphics.fillPolygon(clone.toAwtPolygon());

            return bufferedImage;
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);

            int lineX = (int) Math.round(layoutView.getWidth() * allScale);
            g.setColor(new Color(0, 230, 115, 242));
            g.drawLine(lineX, 0, lineX, getHeight());

            for (Node value : layoutView.nodeIdMap.values()) {
                int x = (int) Math.round(value.getX() * allScale);
                int y = (int) Math.round(value.getY() * allScale);

                Image image = nodeImageMap.get(value.getId());
                if (image != null) {
                    Graphics graphics = g.create(x, y, image.getWidth(null), image.getHeight(null));
                    try {
                        graphics.drawImage(image, 0, 0, null);
                    } finally {
                        graphics.dispose();
                    }
                }
            }

            repaint();
        }
    }

    public static class ComboBoxItem {
        private Integer id;
        private String label;

        public ComboBoxItem(Integer id, String label) {
            this.id = id;
            this.label = label;
        }

        @Override
        public String toString() {
            return label;
        }
    }


}
